require 'optparse'
require 'yaml'

module Orator

  # Handles the command line interface for Orator.
  class CLI

    DEFAULT_OPTIONS = {
      :command   => :help,
      :file      => "config/orator_config.yml",
      :daemonize => false
    }

    def initialize
      @options = {}.merge DEFAULT_OPTIONS
    end

    def parse_arguments(args)
      @opt_parser = OptionParser.new do |opts|

        opts.on('--config FILE', "Loads the configuration settings from FILE.") do |file|
          @options[:file] = file
        end

        opts.on('-cCOMMAND', '--command COMMAND', "The command to run.") do |command|
          @options[:command] = command
        end

        opts.on('-d', '--[no-]daemonize', "Whether or not to daemonize the process.") do |d|
          @options[:daemonize] = d
        end

        opts.on('-D', '--debug', "Run Orator in debug mode.") do
          Orator.debug = true
        end

        opts.on('-h', '--help', "Shows this message.") { puts opts; exit }
        opts.on('-v', '--version', "Shows the version of orator.") do
          puts Orator::VERSION
          exit
        end

        opts.on('-s', '--slient', "Runs orator silently.") do
          $stdout = $stdin = File.open("/dev/null", 'w')
        end

        opts.separator ""
        opts.separator "Valid Commands:"
        opts.separator "\tstart: start the orator server."
        opts.separator "\tstop: stop the orator server."
        opts.separator "\tstatus: checks the status of the server.  Exits 1 if it's down."
      end

      @opt_parser.parse!(args)
    end

    def handle_command
      send(@options[:command])
    end

    def start
      daemonize? do
        load_orators
        require_files
        server = Orator::Server.new(yaml_options[:server_options])
        router = Router.routers.first

        puts "Starting server..." if Orator.debug
        server.run(router)
      end
    end

    def stop
      if File.exists? yaml_options[:pid_file]
        Process.kill 1, File.open(yaml_options[:pid_file], 'r').read.to_i
        puts "Stopped Orator."
        File.unlink yaml_options[:pid_file]
      end
    end

    def status
      check_pid_file if File.exists? yaml_options[:pid_file]
      puts "Not running."
      exit 1
    rescue ProcessExistsError
      puts "Up, running."
    end

    def help
      puts @opt_parser
      exit
    end

    private

    # Grab the options from the options file.
    #
    # @returns [Hash]
    def yaml_options
      @yaml_options ||= YAML::load_file @options[:file]
    end

    # Checks the PID file (assuming it exists) to see if the process is still
    # running.
    #
    # @raises [ProcessExistsError] if the process is still running.
    def check_pid_file
      pid = File.open(yaml_options[:pid_file], 'r').read.to_i

      begin
        Process.kill 0, pid
        raise ProcessExistsError
      rescue Errno::ESRCH
      end
    end

    # Loads the orators from the YAML file by requiring each file.
    #
    # @return [void]
    def load_orators
      requires yaml_options[:orators]
    end

    # This loads files that are deemed required by the configuration file.
    #
    # @return [void]
    def require_files
      requires yaml_options[:require]
    end

    # This takes an array or a string snd requires the files that it can reach.
    #
    # @param [Array<String>, String] the file(s) to require.
    # @return [void]
    def requires(files)
      files = [files] unless files.is_a? Array

      files.each do |r|
        next unless r
        Dir[r].each { |f| load f }
      end
    end

    # Daemonizes the block, if it's applicable.
    #
    # @yields [] in the new process, if daemonized; otherwise, a normal yield.
    def daemonize?
      if @options[:daemonize]
        check_pid_file if File.exists? yaml_options[:pid_file]
        child_process = fork do
          $stdout = $stderr = File.open(yaml_options[:log_file], 'a')
          $stdout.sync = true

          yield
        end

        File.open(yaml_options[:pid_file], 'w') { |f| f.write child_process }
        Process.detach child_process
      else
        yield
      end
    end

    class ProcessExistsError < StandardError; end

  end
end
